package org.jeecgframework.tinydao.aop;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ognl.Ognl;
import ognl.OgnlException;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.engine.jdbc.internal.BasicFormatterImpl;
import org.jeecgframework.tinydao.annotation.Arguments;
import org.jeecgframework.tinydao.annotation.ResultType;
import org.jeecgframework.tinydao.annotation.Sql;
import org.jeecgframework.tinydao.def.TinyDaoConstants;
import org.jeecgframework.tinydao.hibernate.dao.IGenericBaseCommonDao;
import org.jeecgframework.tinydao.pojo.TinyDaoPage;
import org.jeecgframework.tinydao.spring.rowMapper.GenericRowMapper;
import org.jeecgframework.tinydao.spring.rowMapper.TinyColumnMapRowMapper;
import org.jeecgframework.tinydao.spring.rowMapper.TinyColumnOriginalMapRowMapper;
import org.jeecgframework.tinydao.util.FreemarkerParseFactory;
import org.jeecgframework.tinydao.util.TinyDaoUtil;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.simple.ParameterizedBeanPropertyRowMapper;



/** 
* @ClassName: TinyDaoHandler 
* @Description: tinyDao拦截器 
* @author zx zx2009428@163.com
* @date 2015年3月21日 下午10:02:33 
*  
*/
public class TinyDaoHandler implements MethodInterceptor{
	private static final Logger logger = Logger.getLogger(TinyDaoHandler.class);
	private JdbcTemplate jdbcTemplate;
	private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
	private IGenericBaseCommonDao tinyDaoHiberCommonDao;
	private BasicFormatterImpl formatter = new BasicFormatterImpl();
	
	private String UPPER_KEY = "upper";
	private String LOWER_KEY = "lower";
	/**
	 * map的关键字类型 三个值
	 */
	private String keyType = "origin";
	private boolean formatSql = false;
	private boolean showSql = false;
	private String dbType;
	
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		//动态获取方法
		Method method = invocation.getMethod();
		//动态获取方法参数
		Object[] args = invocation.getArguments();
		//返回结果
		Object returnObject = null;
		//SQL模板
		String templateSql = null;
		//SQL模板参数
		Map<String,Object> sqlParamsMap = new HashMap<String,Object>();
		//分页参数
		TinyDaoPage pageSetting = new TinyDaoPage();
		
		//check 排队是否是抽象方法，如果是非抽象方法，则不执行MiniDao拦截器
		if(!TinyDaoUtil.isAbstract(method)){
			return invocation.proceed();
		}
		
		//0.判断是否是Hibernate实体维护方法，如果是执行Hibernate方式实体维护
		Map<String,Object> rs = new HashMap<String,Object>();
		if(miniDaoHiber(rs,method,args)){
			return rs.get("returnObject");
		}
		//1.装载SQL模板，所需参数
		templateSql = installDaoMetaData(pageSetting,method,sqlParamsMap,args);
		//2.解析SQL模板,返回可执行SQL
		String executeSql = parseSqlTemplate(method,templateSql,sqlParamsMap);
		
		//3.组装Sql占位符参数
		Map<String,Object> sqlMap = installPlaceholderSqlparam(executeSql,sqlParamsMap);
		//4.获取sql执行返回值
		returnObject = getReturnTinyDaoResult(dbType,pageSetting,jdbcTemplate,method,executeSql,sqlMap);
		return returnObject;
	}

	/** 
	* @Title: getReturnTinyDaoResult 
	* @Description: 获取tinyDao处理结果集
	* @param @param dbType2
	* @param @param pageSetting
	* @param @param jdbcTemplate2
	* @param @param method
	* @param @param executeSql
	* @param @param sqlMap
	* @param @return    设定文件 
	* @return Object    返回类型 
	* @throws 
	*/
	private Object getReturnTinyDaoResult(String dbType2,
			TinyDaoPage pageSetting, JdbcTemplate jdbcTemplate2, Method method,
			String executeSql, Map<String, Object> sqlMap) {
		 //调用SpringJdbc引擎，执行sql 获取返回值
		//获取返回值类型[Map Object List<Object> List<Map> 基础类型 ]
		String methodName = method.getName();
		//判断是否非查询方法
		if(checkActiveKey(methodName)){
			if(sqlMap!=null){
				return namedParameterJdbcTemplate.update(executeSql, sqlMap);
			}else{
				return jdbcTemplate.update(executeSql);
			}
		}else if(checkBatchKey(methodName)){//如果是批量
			return batchUpdate(jdbcTemplate,executeSql);
		}else{//如果是查询操作
			Class<?> returnType = method.getReturnType();
			if(returnType.isPrimitive()){//如果返回类型是基础类型 或者void
				Number number = jdbcTemplate2.queryForObject(executeSql, BigDecimal.class);
				if("int".equals(returnType)){
					return number.intValue();
				}else if("long".equals(returnType)){
					return number.longValue();
				}else if("double".equals(returnType)){
					return number.doubleValue();
				}
			}else if(returnType.isAssignableFrom(List.class)){//如果返回值是List
				int page = pageSetting.getPage();
				int rows = pageSetting.getRows();
				if(page!=0&&rows!=0){
					executeSql = TinyDaoUtil.createPageSql(dbType,executeSql,page,rows);
				}
				ResultType  resultType = method.getAnnotation(ResultType.class);
				String[] values = null;
				if(resultType!=null){
					values = resultType.value();
				}
				
				if(values==null||values.length==0||"java.util.Map".equals(values[0])){
					if(sqlMap!=null){
						return namedParameterJdbcTemplate.query(executeSql, sqlMap, getColumnMapRowMapper());
					}else{
						return jdbcTemplate.query(executeSql, getColumnMapRowMapper());
					}
				}else {
					Class clazz = null;
					try{
						clazz = Class.forName(values[0]);
					}catch(Exception e){
						e.printStackTrace();
					}
					
					if(sqlMap!=null){
						return namedParameterJdbcTemplate.query(executeSql, sqlMap, new GenericRowMapper(clazz));
					}else{
						return jdbcTemplate.query(executeSql, new GenericRowMapper(clazz));
					}
				}
						
			}else if(returnType.isAssignableFrom(Map.class)){
				if(sqlMap!=null){
					return (Map)namedParameterJdbcTemplate.queryForObject(executeSql, sqlMap,getColumnMapRowMapper());
				}else{
					return (Map)jdbcTemplate.queryForObject(executeSql, getColumnMapRowMapper());
				}
			}else if(returnType.isAssignableFrom(String.class)){
				try{
				 if(sqlMap!=null){
					 return namedParameterJdbcTemplate.queryForObject(executeSql, sqlMap, String.class);
				 }else {
					 return jdbcTemplate.queryForObject(executeSql, String.class);
				 }	
				}catch(Exception e){
					e.printStackTrace();
					return null;
				}
			}else if(TinyDaoUtil.isWrapClass(returnType)){
				try{
					if(sqlMap!=null){
						return namedParameterJdbcTemplate.queryForObject(executeSql, sqlMap, returnType);
						
					}else{
						return jdbcTemplate.queryForObject(executeSql, returnType);
					}
				}catch(Exception e){
					e.printStackTrace();
				}
			}else{
				RowMapper<?> rm = ParameterizedBeanPropertyRowMapper.newInstance(returnType);
				try{
					if(sqlMap!=null){
						return namedParameterJdbcTemplate.queryForObject(executeSql, sqlMap, rm);
					}else{
						return jdbcTemplate.queryForObject(executeSql, rm);
					}
				}catch (EmptyResultDataAccessException e) {  
		            return null;  
		        }
			}
			
		}
		return null;
	}

	/** 
	* @Title: getColumnMapRowMapper 
	* @Description: 根据参数设置map的key大小写
	* @param @return    设定文件 
	* @return RowMapper<Map<String,Object>>    返回类型 
	* @throws 
	*/
	private RowMapper<Map<String,Object>> getColumnMapRowMapper() {
		if(getKeyType().equalsIgnoreCase(LOWER_KEY)){
			return new TinyColumnMapRowMapper();
		}else if(getKeyType().equalsIgnoreCase(UPPER_KEY)){
			return new ColumnMapRowMapper();
		}else{
			return new TinyColumnOriginalMapRowMapper();
		}
	 
	}

	 

	/** 
	* @Title: batchUpdate 
	* @Description: 批处理
	* @param @param jdbcTemplate2
	* @param @param executeSql
	* @param @return    设定文件 
	* @return int[]    返回类型 
	* @throws 
	*/
	private int[] batchUpdate(JdbcTemplate jdbcTemplate2, String executeSql) {
		String[] sqls = executeSql.split(";");
		if(sqls.length<100){
			return jdbcTemplate.batchUpdate(sqls);
		}
		int[] result = new int[sqls.length];
		List<String> sqlList = new ArrayList<String>();
		for(int i =0;i<sqls.length;i++){
			sqlList.add(sqls[i]);
			if(i%100==0){
				addResulArray(result,i+1,jdbcTemplate.batchUpdate(sqlList.toArray(new String[0])));
				sqlList.clear();
			}
		}
		addResulArray(result,sqls.length,jdbcTemplate.batchUpdate(sqlList.toArray(new String[0])));
		return result;
	}

	/** 
	* @Title: addResulArray 
	* @Description: 把批量处理的结果拼接起来
	* @param @param result
	* @param @param index
	* @param @param batchUpdate    设定文件 
	* @return void    返回类型 
	* @throws 
	*/
	private void addResulArray(int[] result, int index, int[] batchUpdate) {
	    int length = batchUpdate.length;
	    for(int i = 0;i<length;i++){
	    	result[index-length+i] = batchUpdate[i];
	    }
		
	}

	private boolean checkBatchKey(String methodName) {
		String keys[] = TinyDaoConstants.INF_METHOD_BATCH.split(",");
		for(String s:keys){
			if(methodName.startsWith(s)){
				return true;
			}
		}
		return false;
	}

	/** 
	* @Title: checkActiveKey 
	* @Description: 判断是否是非查询方法
	* @param @param methodName
	* @param @return    设定文件 
	* @return boolean    返回类型 
	* @throws 
	*/
	private boolean checkActiveKey(String methodName) {
		String keys[]  = TinyDaoConstants.INF_METHOD_ACTIVE.split(",");
		for(String s:keys){
			if(methodName.startsWith(s)){
				return true;
			}
		}
		return false;
	}

	/** 
	* @Title: installPlaceholderSqlparam 
	* @Description: TODO(这里用一句话描述这个方法的作用) 
	* @param @param executeSql
	* @param @param sqlParamsMap
	* @param @return
	* @param @throws OgnlException    设定文件 
	* @return Map<String,Object>    返回类型 
	* @throws 
	*/
	private Map<String, Object> installPlaceholderSqlparam(String executeSql,
			Map<String, Object> sqlParamsMap) throws OgnlException {
	Map<String,Object> map = new HashMap<String,Object>();
	String regEx = ":[ tnx0Bfr]*[0-9a-z.A-Z]+";//表示以：开头[0-9或者A-Z大小写]的任意字符，超过一个长度
	Pattern pat = Pattern.compile(regEx);
	Matcher m = pat.matcher(executeSql);
	while(m.find()){
		logger.debug("Match["+m.group()+"] at positions "+ m.start()+"-"+(m.end()-1));
		String ognl_key = m.group().replace(":", "").trim();
		map.put(ognl_key, Ognl.getValue(ognl_key, sqlParamsMap));
	}
			
		return map;
	}

	/** 
	* @Title: parseSqlTemplate 
	* @Description: 解析SQL模板
	* @param @param method
	* @param @param templateSql
	* @param @param sqlParamsMap
	* @param @return    设定文件 
	* @return String    返回类型 
	* @throws 
	*/
	private String parseSqlTemplate(Method method, String templateSql,
			Map<String, Object> sqlParamsMap) {
		// 1.根据命名规范"接口名_方法名.sql" 获取SQL模板文件路径
		String executeSQl = null;
		 //2.获取SQL模板内容
		//3.通过模板引擎给SQL模板装载参数,解析生成可执行SQL
		if(StringUtils.isNotEmpty(templateSql)){
			executeSQl = new FreemarkerParseFactory().parseTemplateContent(templateSql, sqlParamsMap);
		}else{
			//扫描模板文件规则： 先扫描同位置sql目录，如果没有找到文件再搜索dao目录
			String sqlTempletPath = "/"+method.getDeclaringClass().getName().replace(".", "/").replace("/dao/", "/sql/")+"_"+method.getName()+".sql";
			URL sqlFileUrl = this.getClass().getClassLoader().getResource(sqlTempletPath);
			if(sqlFileUrl==null){
				sqlTempletPath = "/"+method.getDeclaringClass().getName().replace(".", "/")+"_"+method.getName()+".sql";
			}
			logger.debug("TinyDao_SQL_PATH:"+sqlTempletPath);
			executeSQl = new FreemarkerParseFactory().parseTemplate(sqlTempletPath,"utf-8", sqlParamsMap);
		}
		return getSqlText(executeSQl);
	}

	/** 
	* @Title: getSqlText 
	* @Description: 去除回车 制表符 空格键等无效字段，不然批量处理可能报错
	* @param @param executeSQl
	* @param @return    设定文件 
	* @return String    返回类型 
	* @throws 
	*/
	private String getSqlText(String executeSQl) {
		
		return executeSQl.replaceAll("\\n", " ").replaceAll("\\t", " ")
				.replaceAll("\\s{1,}", " ").trim();
		
	}

	/**
	 * @throws Exception  
	* @Title: installDaoMetaData 
	* @Description: 装载SQL模板参数
	* @param @param pageSetting
	* @param @param method
	* @param @param sqlParamsMap
	* @param @param args
	* @param @return    设定文件 
	* @return String     templateSql(@SQL标签的SQL)
	* @throws 
	*/
	private String installDaoMetaData(TinyDaoPage pageSetting, Method method,
			Map<String, Object> sqlParamsMap, Object[] args) throws Exception {
		String templateSql = null;
		//如果方法参数大于1个的话方法必须使用注解标签Arguments
		boolean arguments_flag = method.isAnnotationPresent(Arguments.class);
		if(arguments_flag){
			//1.获取方法的参数注解
			Arguments arguments = method.getAnnotation(Arguments.class);
			logger.debug("@Arguments-----------"+Arrays.toString(arguments.value()));
			if(arguments.value().length > args.length ){
				//校验机制  如果注解标签参数数目大于方法的参数数目则抛出异常
				throw new Exception("[注解标签]参数数目，不能大于[方法参数]参数数目");
			}
			//将args转换成键值对，封装成Map对象
			
			int args_num = 0;
			for(String v:arguments.value()){
				//支持多数据分页
				if(v.equalsIgnoreCase("page")){
					pageSetting.setPage(Integer.parseInt(args[args_num].toString()));
				}
				
				if(v.equalsIgnoreCase("rows")){
					pageSetting.setRows(Integer.parseInt(args[args_num].toString()));
				}
				sqlParamsMap.put(v, args[args_num]);
				args_num++;
			}
			
		}else{//没有使用注解
			if(args.length>1){
				throw new Exception("方法参数数目>=2,方法必须使用注解标签@Arguments");
			}else if(args.length==1){
				//用户没有使用@Arguments注解并且方法只有一个参数，模板引用参数默认是 dto
				sqlParamsMap.put(TinyDaoConstants.SQL_FTL_DTO, args[0]);
			}
		}
		
		//2.获取方法的SQL标签
		if(method.isAnnotationPresent(Sql.class)){
			Sql sql = method.getAnnotation(Sql.class);
			if(StringUtils.isNotEmpty(sql.value())){
				templateSql = sql.value();
			}
			logger.debug("@Sql---------------------"+sql.value());
		}
		return templateSql;
	}

	/** 
	* @Title: miniDaoHiber 
	* @Description: tinyDao支持实体维护
	* 向下兼容HiberNate实体维护方式，实体的增删改查Sql自动生成不需要写Sql
	* @param @param rs
	* @param @param method
	* @param @param args
	* @param @return    设定文件 
	* @return boolean    返回类型 
	* @throws 
	*/
	private boolean miniDaoHiber(Map<String, Object> rs, Method method,
			Object[] args) {
		// 是否采用Hibernate方式，进行实体维护，不需要生成SQL
		//如果是持久化对象，则调用HIbernate进行持久化维护
		if(TinyDaoConstants.METHOD_SAVE_BY_HIBER.equals(method.getName())){
			tinyDaoHiberCommonDao.save(args[0]);
			return true;
			
		}
		
		if(TinyDaoConstants.METHOD_GET_BY_ID_HIBER.equals(method.getName())){
			//获取dao方法与实体配置
			Class<?> clazz = (Class<?>) args[0];
			rs.put("returnObject", tinyDaoHiberCommonDao.get(clazz, args[1].toString()));
			return true;
		}
		
		if(TinyDaoConstants.METHOD_GET_BY_ENTITY_HIBER.equals(method.getName())){
			//获取主键名
			rs.put("returnObject", tinyDaoHiberCommonDao.get(args[0]));
			return true;
		}
		
		if(TinyDaoConstants.METHOD_UPDATE_BY_HIBER.equals(method.getName())){
			 tinyDaoHiberCommonDao.saveOrUpdate(args[0]);
			return true;
		}
		
		if(TinyDaoConstants.METHOD_DELETE_BY_HIBER.equals(method.getName())){
			 tinyDaoHiberCommonDao.delete(args[0]);
			return true;
		}
		
		if(TinyDaoConstants.METHOD_DELETE_BY_ID_HIBER.equals(method.getName())){
			 Class<?> clazz = (Class<?>) args[0];
			 tinyDaoHiberCommonDao.deleteEntityById(clazz, args[1].toString());
			return true;
		}
		
		if(TinyDaoConstants.METHOD_LIST_BY_HIBER.equals(method.getName())){
			 rs.put("returnObject", tinyDaoHiberCommonDao.loadAll(args[0]));
			return true;
		}
		
		
		return false;
	}

	public String getKeyType() {
		return keyType;
	}

	public void setKeyType(String keyType) {
		this.keyType = keyType;
	}

	public JdbcTemplate getJdbcTemplate() {
		return jdbcTemplate;
	}

	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	public NamedParameterJdbcTemplate getNamedParameterJdbcTemplate() {
		return namedParameterJdbcTemplate;
	}

	public void setNamedParameterJdbcTemplate(
			NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
		this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
	}

	public IGenericBaseCommonDao getTinyDaoHiberCommonDao() {
		return tinyDaoHiberCommonDao;
	}

	public void setTinyDaoHiberCommonDao(IGenericBaseCommonDao tinyDaoHiberCommonDao) {
		this.tinyDaoHiberCommonDao = tinyDaoHiberCommonDao;
	}

	public BasicFormatterImpl getFormatter() {
		return formatter;
	}

	public void setFormatter(BasicFormatterImpl formatter) {
		this.formatter = formatter;
	}

	public String getUPPER_KEY() {
		return UPPER_KEY;
	}

	public void setUPPER_KEY(String uPPER_KEY) {
		UPPER_KEY = uPPER_KEY;
	}

	public String getLOWER_KEY() {
		return LOWER_KEY;
	}

	public void setLOWER_KEY(String lOWER_KEY) {
		LOWER_KEY = lOWER_KEY;
	}

	public boolean isFormatSql() {
		return formatSql;
	}

	public void setFormatSql(boolean formatSql) {
		this.formatSql = formatSql;
	}

	public boolean isShowSql() {
		return showSql;
	}

	public void setShowSql(boolean showSql) {
		this.showSql = showSql;
	}

	public String getDbType() {
		return dbType;
	}

	public void setDbType(String dbType) {
		this.dbType = dbType;
	}

	 

}
